/***********************************************************************************************//**
 * \file   advertisement.c
 * \brief  Functions and data related to connectable advertisement
 ***************************************************************************************************
 * <b> (C) Copyright 2015 Silicon Labs, http://www.silabs.com</b>
 ***************************************************************************************************
 * This file is licensed under the Silabs License Agreement. See the file
 * "Silabs_License_Agreement.txt" for details. Before using this software for
 * any purpose, you must agree to the terms of that agreement.
 **************************************************************************************************/

#include CONFIGURATION_HEADER
#include CALLBACKS_HEADER


#include <stdint.h>
#include <string.h>
#include <stdio.h>

/* BG stack headers */
#include "bg_types.h"
#ifdef HOST
#include "gecko_bglib.h"
#else /* !HOST */
#include "native_gecko.h"
#endif /* !HOST */
#include "infrastructure.h"

/* application specific headers*/
#include "event_control.h"

/* features */
#ifdef SILABS_AF_PLUGIN_BEACON
#include "beacon.h"
#endif /* SILABS_AF_PLUGIN_BEACON */
#ifdef SILABS_AF_PLUGIN_CONNECTION
#include "connection.h"
#endif /* SILABS_AF_PLUGIN_CONNECTION */

/* Own header */
#include "advertisement.h"

/***********************************************************************************************//**
 * @addtogroup Features
 * @{
 **************************************************************************************************/

/***********************************************************************************************//**
 * @addtogroup adv
 * @{
 **************************************************************************************************/

/***************************************************************************************************
 Local Macros and Definitions
 ***************************************************************************************************/

#ifdef SILABS_AF_PLUGIN_ADVERTISEMENT_ADV_CUSTOM
#define ADVERTISEMENT_ADV_TYPE1                  0
#define ADVERTISEMENT_ADV_TYPE2                  0
#else
#define ADVERTISEMENT_ADV_TYPE1                  SILABS_AF_PLUGIN_ADVERTISEMENT_ADV_TYPE1
#define ADVERTISEMENT_ADV_TYPE2                  SILABS_AF_PLUGIN_ADVERTISEMENT_ADV_TYPE2
#endif /* SILABS_AF_PLUGIN_ADVERTISEMENT_ADV_CUSTOM */

/* Timeout needed when switching advertising content in ms
 * todo remove when no longer necessary */
#define ADV_TIMEOUT 				 250

/* Multiplication factor which transitions from AppBuilder's Time in millisecs, and the BLE stack's
 * Time in multiples of 0.625 msec */
#define ADV_INT_MUL_FACTOR 			 1.6

/***************************************************************************************************
 Local Type Definitions
 **************************************************************************************************/

/* States of Advertisement State Machine */
typedef enum
{
	ADV_BEACONING,
	ADV_WAITING_FOR_BEACONING,
	ADV_WAITING_FOR_ADVERTISING,
	ADV_ADVERTISING,
	ADV_ADVERTISING_WAIT_FOR_OPTION_CHANGE,
	ADV_ADVERTISING_STOPPED,
	ADV_CONNECTION
} advState_t;

/* Advertising Interval Table */
typedef struct
{
	uint16_t advAdvertisingIntervalMin;  // Value multiplied by 0.625 ms
	uint16_t advAdvertisingIntervalMax;  // Value multiplied by 0.625 ms
	uint16_t advDuration; // 16383 (0x3FFF) sec or larger corresponds to infinite advertisement
	uint8_t advType;
} advAdvertisingParameters_t;
/***************************************************************************************************
 Public Variables
 **************************************************************************************************/

EmberEventControl advTimerEvt =
{ EVENT_INACTIVE, 0 };

/***************************************************************************************************
 Local Variables
 **************************************************************************************************/
/* Advertising Parameter Table */
static const advAdvertisingParameters_t advAdvertisingParameters[] =
{
{ SILABS_AF_PLUGIN_ADVERTISEMENT_ADV_INT_MIN1 * ADV_INT_MUL_FACTOR,
		SILABS_AF_PLUGIN_ADVERTISEMENT_ADV_INT_MAX1 * ADV_INT_MUL_FACTOR,
		SILABS_AF_PLUGIN_ADVERTISEMENT_ADV_TIME1, ADVERTISEMENT_ADV_TYPE1 },
{ SILABS_AF_PLUGIN_ADVERTISEMENT_ADV_INT_MIN2 * ADV_INT_MUL_FACTOR,
		SILABS_AF_PLUGIN_ADVERTISEMENT_ADV_INT_MAX2 * ADV_INT_MUL_FACTOR,
		SILABS_AF_PLUGIN_ADVERTISEMENT_ADV_TIME2, ADVERTISEMENT_ADV_TYPE2 } };

static uint8_t advAdvertisingParametersSize = sizeof(advAdvertisingParameters)
		/ sizeof(advAdvertisingParameters_t);

/* Advertisement State Machine */
static advState_t advState;

/* This Index variable stores the current position in the Advertisement Parameter Table
 * (Which Advertising Parameters should currently be active) */
static uint8_t advIndex = 0;

#ifdef SILABS_AF_PLUGIN_ADVERTISEMENT_ADV_CUSTOM
static uint8_t *advCustomContent = NULL;
static uint8_t advCustomContentLength = 0;
static uint8_t *advScanRspCustomContent = NULL;
static uint8_t advScanRspCustomContentLength = 0;
#endif

/***************************************************************************************************
 Static Function Declarations
 **************************************************************************************************/

static uint8_t advHandleAdvertising(void);
static uint8_t advStartAdvertising(void);

/***************************************************************************************************
 Function Definitions
 **************************************************************************************************/

void advInit(void)
{
#ifdef SILABS_AF_PLUGIN_ADVERTISEMENT_ADV_CUSTOM
	advState = ADV_ADVERTISING_STOPPED;
#else
	/* Return to initial advertising mode (connectable mode) */
	advState = ADV_WAITING_FOR_ADVERTISING;

	/* Stop advertisement*/
	gecko_cmd_le_gap_set_mode(le_gap_non_connectable, le_gap_non_discoverable);

	/* we need to wait here some time with advertisement stopped until changes are registered in
	 * stack and advertisement can be started again with a different user defined advertising message */
	eventControlSetDelayMS(&advTimerEvt, ADV_TIMEOUT);

	emberAfPluginAdvertisingStartedCallback(); /* call callback */

#endif /* SILABS_AF_PLUGIN_ADVERTISEMENT_ADV_CUSTOM */

}

void advSetup(void)
{
	switch (advState)
	{
	case ADV_WAITING_FOR_BEACONING:
		/* Waiting time for nonconnectable advertising (beaconing) elapsed */
#ifdef SILABS_AF_PLUGIN_BEACON
		/* Start nonconnectable advertising (beaconing) */
		bcnSetupAdvBeaconing();
		advState = ADV_BEACONING; /* Update State Machine state */
#endif
		break;

	case ADV_WAITING_FOR_ADVERTISING:
		/* Waiting time for connectable advertising elapsed */
		/* Start connectable advertising  */
		advStartAdvertising();
		advState = ADV_ADVERTISING; /* Update State Machine state */
		break;

	case ADV_ADVERTISING:
		/* Switch off advertising so option change will be possible (stack workaround) */
		gecko_cmd_le_gap_set_mode(le_gap_non_connectable,
				le_gap_non_discoverable);
		eventControlSetDelayMS(&advTimerEvt, ADV_TIMEOUT); /* Start timer with 100 ms */
		advState = ADV_ADVERTISING_WAIT_FOR_OPTION_CHANGE;

		break;

	case ADV_ADVERTISING_WAIT_FOR_OPTION_CHANGE:
		/* It's time to stop advertising or step in the Advertising Parameter table) */
		if (!advHandleAdvertising())
		{
			advState = ADV_ADVERTISING; /* Update State Machine state */
		}
		else
		{
			/* Stop advertisement*/
			gecko_cmd_le_gap_set_mode(le_gap_non_connectable,
					le_gap_non_discoverable);
			emberAfPluginAdvertisingStoppedCallback(); /* call callback */
			advState = ADV_ADVERTISING_STOPPED; /* Update State Machine state */
		}
		break;

	case ADV_BEACONING:
	case ADV_ADVERTISING_STOPPED:
	case ADV_CONNECTION:
		break;
	}

}

void advSwitchAdvMessage(void)
{
	switch (advState)
	{
	case ADV_BEACONING:
	case ADV_ADVERTISING_STOPPED:
		/* Stop advertisement*/
		gecko_cmd_le_gap_set_mode(le_gap_non_connectable,
				le_gap_non_discoverable);

		/* we need to wait here some time with advertisement stopped until changes are registered in
		 * stack and we can advertise again with a different user defined advertising message */
		eventControlSetDelayMS(&advTimerEvt, ADV_TIMEOUT);

#ifdef SILABS_AF_PLUGIN_ADVERTISEMENT_ADV_CUSTOM
		/* Set advertising and scan response data content */
		if (advScanRspCustomContent)
		{
			gecko_cmd_le_gap_set_adv_data(true, advScanRspCustomContentLength, advScanRspCustomContent);
		}

		if (advCustomContent)
		{
			gecko_cmd_le_gap_set_adv_data(false, advCustomContentLength, advCustomContent);
		}

#endif /* SILABS_AF_PLUGIN_ADVERTISEMENT_ADV_CUSTOM */

		emberAfPluginAdvertisingStartedCallback(); /* call callback */

		advState = ADV_WAITING_FOR_ADVERTISING; /* Update State Machine state */
		break;

#ifdef SILABS_AF_PLUGIN_BEACON
		case ADV_ADVERTISING:
		/* Stop advertisement*/
		gecko_cmd_le_gap_set_mode(le_gap_non_connectable, le_gap_non_discoverable);

		/* We need to wait here some time with advertisement stopped until changes are registered in
		 * stack and we can advertise again with a different user defined advertising message */
		eventControlSetDelayMS(&advTimerEvt, ADV_TIMEOUT);
		advState = ADV_WAITING_FOR_BEACONING; /* Update State Machine state */

		break;
#endif /* SILABS_AF_PLUGIN_BEACON */
//    case ADV_WAITING_FOR_ADVERTISING:
//    case ADV_ADVERTISING_STOPPED:
//    case ADV_WAITING_FOR_BEACONING:
//    case ADV_CONNECTION:
	default:
		break;
	}
}

void advConnectionStarted(void)
{
	advState = ADV_CONNECTION; /* Reset advertisement state machine */

	/* Stop Adv. Timer
	 * If advertisement mode just changed and Adv. Timer is still running to carry out
	 * advertisement mode switch then stop the timer here to avoid advertisement data change while
	 * connected. */
	eventControlSetInactive(advTimerEvt);
}

#ifdef SILABS_AF_PLUGIN_ADVERTISEMENT_ADV_CUSTOM
void advInitCustomAdvertising(uint8_t dataLen, uint8_t *pData, uint8_t scanDatalen, uint8_t *pScanData)
{

	/* Stop advertisement*/
	gecko_cmd_le_gap_set_mode(le_gap_non_connectable, le_gap_non_discoverable);

	/* Stop timer */
	eventControlSetInactive(advTimerEvt);

	/* Return to initial advertising mode (connectable mode) */
	advState = ADV_ADVERTISING_STOPPED;

	if (pScanData)
	{
		/* Set scan response data content */
		gecko_cmd_le_gap_set_adv_data(true, scanDatalen, pScanData);
		advScanRspCustomContent = pScanData;
		advScanRspCustomContentLength = scanDatalen;

	}
	if (pData)
	{
		/* Set advertising data content */
		gecko_cmd_le_gap_set_adv_data(false, dataLen, pData);
		advCustomContent = pData;
		advCustomContentLength = dataLen;
	}

}
#endif

void advStopAdvertising(void)
{

	/* Stop advertisement*/
	gecko_cmd_le_gap_set_mode(le_gap_non_connectable, le_gap_non_discoverable);

	/* Stop Adv. Timer
	 * If advertisement mode just changed and Adv. Timer is still running to carry out
	 * advertisement mode switch then stop the timer here to avoid advertisement data change while
	 * connected. */
	eventControlSetInactive(advTimerEvt);

	advState = ADV_ADVERTISING_STOPPED;

	emberAfPluginAdvertisingStoppedCallback();

}

/***************************************************************************************************
 Static Function Declarations
 **************************************************************************************************/

/***********************************************************************************************//**
 *  \brief  Handle advertising
 *  \return  0 for success, 0xff for error
 ***************************************************************************************************/
static uint8_t advHandleAdvertising(void)
{
	/* Stop advertisement */
	gecko_cmd_le_gap_set_mode(le_gap_non_discoverable, le_gap_non_connectable);

	/* Evaluated from left to right, so buffer overflow may not happen */
	if ((advAdvertisingParametersSize <= advIndex)
			|| (advAdvertisingParameters[advIndex].advDuration == 0))
	{
		/* The end of advertisement parameter table */
		advIndex = 0;
		eventControlSetInactive(advTimerEvt); /* Stop timer */
		return 0xff; /* Return error, advertising must stop */
	}
	else
	{
		/* Set new advertising intervals */
		gecko_cmd_le_gap_set_adv_parameters(
				advAdvertisingParameters[advIndex].advAdvertisingIntervalMin,
				advAdvertisingParameters[advIndex].advAdvertisingIntervalMax,
				7);

		/* Start advertising */
#ifdef SILABS_AF_PLUGIN_ADVERTISEMENT_ADV_CUSTOM
		if (advCustomContent)
		{
			gecko_cmd_le_gap_set_mode(le_gap_user_data, le_gap_undirected_connectable);
		}
#else
		if (advAdvertisingParameters[advIndex].advType
				== ADV_GENERAL_DISCOVERABLE)
		{
			gecko_cmd_le_gap_set_mode(le_gap_general_discoverable,
					le_gap_undirected_connectable);
		}
		else
		{
			gecko_cmd_le_gap_set_mode(le_gap_limited_discoverable,
					le_gap_undirected_connectable);
		}
#endif /* SILABS_AF_PLUGIN_ADVERTISEMENT_ADV_CUSTOM */

		/* Start timer to update adv parameters*/
		if (advAdvertisingParameters[advIndex].advDuration < 0x3FFF)
		{
			/* Use the quarter-sec API, multiply by 4 to measure seconds */
			eventControlSetDelayQS(&advTimerEvt,
					(advAdvertisingParameters[advIndex].advDuration * 4));
			advIndex++;
		}
		else
		{
			eventControlSetInactive(advTimerEvt); /* Stop timer, advertising is to continue indefinitely*/
		}

	}

	return 0;
}

/***********************************************************************************************//**
 *  \brief  Start advertising
 *  \return  0 for success, 0xff for error
 ***************************************************************************************************/
static uint8_t advStartAdvertising(void)
{
	/* Jump to first position in advertisement table */
	advIndex = 0;
	/* Start process of setting new advertisement parameters from advertisment parameter table */
	return (advHandleAdvertising());
}

/** @} (end addtogroup adv) */
/** @} (end addtogroup Features) */

